package code.google.jcontainer;

import code.google.jcontainer.annotation.Container;

import java.io.File;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * ContainerFactory is the entry to create Container
 *
 * @author <a href="mailto:jfox.young@gmail.com">Young Yang</a>
 */
public class ContainerFactory {

    /**
     * the package scope for scanning annotations
     */    
    private String[] scanPackages = null;

    private File[] scanPaths = null;

    /**
     * Used to load and hold all the loaded annotation classes
     */
    private AnnotationRepository annotationRepository;

    private final Map<String, AbstractContainer> containers = new HashMap<String, AbstractContainer>(5);

    /**
     * Create a new ContainerFactory by scanning filter packages
     * @param scanPackages, the package scope for scanning annotations, support regexp, such as: a.b.c, a.b.*, a.*.c
     * @return created ContainerFactory
     */
    public static ContainerFactory scanPackages(String... scanPackages) {
        return scanPaths(null, scanPackages);
    }

    /**
     * scan the specified path to find containers
     * @param scanPaths jars or class directories
     */
    public static ContainerFactory scanPaths(File... scanPaths) {
        if(scanPaths != null && scanPaths.length !=0 && scanPaths[0] == null) {
            scanPaths = null;
        }
        return scanPaths(scanPaths, ".*");
    }

    /**
     * * scan the specified path with the filter packages to find containers
     * @param scanPaths jars or class directories, null means whole classpath
     * @param scanPackages filter packages, support regexp, such as: a.b.c, a.b.*, a.*.c, null means all packages 
     */
    public static ContainerFactory scanPaths(File[] scanPaths, String... scanPackages) {
        if(scanPaths != null && scanPaths.length == 1 && scanPaths[0] ==null){
            scanPaths = null;
        }
        if(scanPackages == null || scanPackages.length == 0) {
            scanPackages = new String[]{".*"}; // will scan all package
        }
        return new ContainerFactory(scanPaths, scanPackages);
    }

    private ContainerFactory(File[] scanPaths, String... scanPackages) {
        this.scanPaths = scanPaths;
        this.scanPackages = scanPackages;
        annotationRepository = new AnnotationRepository((URLClassLoader)Thread.currentThread().getContextClassLoader());
        annotationRepository.scanAnnotation(scanPaths, scanPackages);
        loadContainers();
    }

    private void loadContainers(){
        Class<?>[] containerClasses = annotationRepository.getAnnotatedClasses(Container.class);
        for(Class<?> containerClass : containerClasses){
            if(!AbstractContainer.class.isAssignableFrom(containerClass) ) {
                throw new ContainerRuntimeException("Class " + containerClass + " annotated with @Container, but doesn't extends " + AbstractContainer.class.getName());
            }
            Class<? extends AbstractContainer> containerClass1 = containerClass.asSubclass(AbstractContainer.class);
            try {
                AbstractContainer container = containerClass1.newInstance();
                containers.put(container.getName(), container);
            }
            catch (Exception e) {
                throw new ContainerRuntimeException("Failed to create container", e);
            }
        }
    }

    /**
     * return annotation repository, used to determine if an annotation is loaded
     */
    public AnnotationRepository getAnnotationRepository() {
        return annotationRepository;
    }

    /**
     * Get the scanned paths
     * @return null means whole classpath
     */
    public File[] getScannedPaths(){
        return scanPaths;
    }

    /**
     * return the package scope for scanning annotations
     * @return null means all packages
     */
    public String[] getScannedPackages() {
        return scanPackages;
    }

    /**
     * Get a container by name, container name is specified by {@link code.google.jcontainer.annotation.Container#name()}
     */
    public AbstractContainer getContainer(String containerName) {
        if(!containers.containsKey(containerName)) {
            throw new NoSuchContainerRuntimeException(containerName);
        }
        AbstractContainer container = containers.get(containerName);
        //only init once
        container.init(annotationRepository);
        return container;
    }

    /**
     * Get all containers
     */
    public Map<String, AbstractContainer> getContainers() {
        return Collections.unmodifiableMap(containers);
    }

}
